// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.linqingying.gitcode.api

import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.core.JsonParseException
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.*
import com.fasterxml.jackson.databind.introspect.VisibilityChecker

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.linqingying.gitcode.exceptions.GitCodeJsonException
import java.awt.image.BufferedImage
import java.io.IOException
import java.io.InputStream
import java.io.Reader
import java.text.SimpleDateFormat
import java.util.*
import javax.imageio.ImageIO

object GitCodeApiContentHelper {
  const val JSON_MIME_TYPE = "application/json"
  const val V3_JSON_MIME_TYPE = "application/vnd.gitcode.v3+json"
  const val V3_HTML_JSON_MIME_TYPE = "application/vnd.gitcode.v3.html+json"
  const val V3_DIFF_JSON_MIME_TYPE = "application/vnd.gitcode.v3.diff+json"

  private val jackson: ObjectMapper = jacksonObjectMapper().genericConfig()
    .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)

  private val gqlJackson: ObjectMapper = jacksonObjectMapper().genericConfig()
    .setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE)

  private fun ObjectMapper.genericConfig(): ObjectMapper =
    this.setDateFormat(SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"))
      .setTimeZone(TimeZone.getDefault())
      .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
      .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
      .enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
      .setSerializationInclusion(JsonInclude.Include.NON_NULL)
      .setVisibility(VisibilityChecker.Std(JsonAutoDetect.Visibility.NONE,
                                           JsonAutoDetect.Visibility.NONE,
                                           JsonAutoDetect.Visibility.NONE,
                                           JsonAutoDetect.Visibility.NONE,
                                           JsonAutoDetect.Visibility.ANY))

  @Throws(GitCodeJsonException::class)
  inline fun <reified T> fromJson(string: String): T = fromJson(string, T::class.java)

  @JvmStatic
  @Throws(GitCodeJsonException::class)
  fun <T> fromJson(string: String, clazz: Class<T>, gqlNaming: Boolean = false): T {
    try {
      return getObjectMapper(gqlNaming).readValue(string, clazz)
    }
    catch (e: JsonParseException) {
      throw GitCodeJsonException("Can't parse GitCode response", e)
    }
  }

  @JvmStatic
  @Throws(GitCodeJsonException::class)
  fun <T> readJsonObject(reader: Reader, clazz: Class<T>, vararg parameters: Class<*>, gqlNaming: Boolean = false): T {
    return readJson(reader, jackson.typeFactory.constructParametricType(clazz, *parameters), gqlNaming)
  }

  @JvmStatic
  @Throws(GitCodeJsonException::class)
  fun <T> readJsonList(reader: Reader, parameterClass: Class<T>): List<T> {
    return readJson(reader, jackson.typeFactory.constructCollectionType(List::class.java, parameterClass))
  }

  @JvmStatic
  @Throws(GitCodeJsonException::class)
  fun <T, U> readJsonMap(reader: Reader, gqlNaming: Boolean = false): Map<T, U> {
    try {
      return getObjectMapper(gqlNaming).readValue(reader, object : TypeReference<Map<T, U>>() {})
    }
    catch (e: JsonProcessingException) {
      throw GitCodeJsonException("Can't parse GitCode response", e)
    }
  }

  @Throws(GitCodeJsonException::class)
  private fun <T> readJson(reader: Reader, type: JavaType, gqlNaming: Boolean = false): T {
    try {
      @Suppress("UNCHECKED_CAST")
      if (type.isTypeOrSubTypeOf(Unit::class.java) || type.isTypeOrSubTypeOf(Void::class.java)) return Unit as T
      return getObjectMapper(gqlNaming).readValue(reader, type)
    }
    catch (e: JsonProcessingException) {
      throw GitCodeJsonException("Can't parse GitCode response", e)
    }
  }

  @JvmStatic
  @Throws(GitCodeJsonException::class)
  fun toJson(content: Any, gqlNaming: Boolean = false): String {
    try {
      return getObjectMapper(gqlNaming).writeValueAsString(content)
    }
    catch (e: JsonProcessingException) {
      throw GitCodeJsonException("Can't serialize GitCode request body", e)
    }
  }

  private fun getObjectMapper(gqlNaming: Boolean = false): ObjectMapper = (if (!gqlNaming) jackson else gqlJackson).apply {
//    registerModule(JavaTimeModule())
    dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX");
     setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
  }

  @JvmStatic
  @Throws(IOException::class)
  fun loadImage(stream: InputStream): BufferedImage {
    return ImageIO.read(stream)
  }
}
